{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "861d2818",
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "d1507859",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import gc\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import re\n",
    "import time\n",
    "from scipy import stats\n",
    "import matplotlib.pyplot as plt\n",
    "import category_encoders as ce\n",
    "import networkx as nx\n",
    "import pickle\n",
    "import lightgbm as lgb\n",
    "import catboost as cat\n",
    "import xgboost as xgb\n",
    "from datetime import timedelta\n",
    "from gensim.models import Word2Vec\n",
    "from io import StringIO\n",
    "from tqdm import tqdm\n",
    "from lightgbm import LGBMClassifier\n",
    "from lightgbm import log_evaluation, early_stopping\n",
    "from sklearn.metrics import roc_curve\n",
    "from scipy.stats import chi2_contingency, pearsonr\n",
    "from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n",
    "from sklearn.feature_extraction import FeatureHasher\n",
    "from sklearn.model_selection import StratifiedKFold, KFold, train_test_split, GridSearchCV\n",
    "from category_encoders import TargetEncoder\n",
    "from sklearn.decomposition import TruncatedSVD\n",
    "from autogluon.tabular import TabularDataset, TabularPredictor, FeatureMetadata\n",
    "from autogluon.features.generators import AsTypeFeatureGenerator, BulkFeatureGenerator, DropUniqueFeatureGenerator, FillNaFeatureGenerator, PipelineFeatureGenerator\n",
    "from autogluon.features.generators import CategoryFeatureGenerator, IdentityFeatureGenerator, AutoMLPipelineFeatureGenerator\n",
    "from autogluon.common.features.types import R_INT, R_FLOAT\n",
    "from autogluon.core.metrics import make_scorer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff0951d4",
   "metadata": {},
   "source": [
    "# 数据导入"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a96f58d3",
   "metadata": {},
   "source": [
    "## 数据导入通用函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6ef1ed67",
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_data_from_directory(directory):\n",
    "    \"\"\"\n",
    "    遍历目录加载所有CSV文件，将其作为独立的DataFrame变量\n",
    "\n",
    "    参数:\n",
    "    - directory: 输入的数据路径\n",
    "    \n",
    "    返回:\n",
    "    - 含有数据集名称的列表\n",
    "    \"\"\"\n",
    "    dataset_names = []\n",
    "    for filename in os.listdir(directory):\n",
    "        if filename.endswith(\".csv\"):\n",
    "            dataset_name = os.path.splitext(filename)[0] + '_data' # 获取文件名作为变量名\n",
    "            file_path = os.path.join(directory, filename)  # 完整的文件路径\n",
    "            globals()[dataset_name] = pd.read_csv(file_path)  # 将文件加载为DataFrame并赋值给全局变量\n",
    "            dataset_names.append(dataset_name)\n",
    "            print(f\"数据集 {dataset_name} 已加载为 DataFrame\")\n",
    "\n",
    "    return dataset_names"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e4b1e0f",
   "metadata": {},
   "source": [
    "## 导入数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "dd412459",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集 AGET_PAY_data 已加载为 DataFrame\n",
      "数据集 ASSET_data 已加载为 DataFrame\n",
      "数据集 CCD_TR_DTL_data 已加载为 DataFrame\n",
      "数据集 MB_PAGEVIEW_DTL_data 已加载为 DataFrame\n",
      "数据集 MB_QRYTRNFLW_data 已加载为 DataFrame\n",
      "数据集 MB_TRNFLW_data 已加载为 DataFrame\n",
      "数据集 NATURE_data 已加载为 DataFrame\n",
      "数据集 PROD_HOLD_data 已加载为 DataFrame\n",
      "数据集 TARGET_data 已加载为 DataFrame\n",
      "数据集 TARGET_VALID_data 已加载为 DataFrame\n",
      "数据集 TR_APS_DTL_data 已加载为 DataFrame\n",
      "数据集 TR_IBTF_data 已加载为 DataFrame\n",
      "数据集 TR_TPAY_data 已加载为 DataFrame\n"
     ]
    }
   ],
   "source": [
    "train_load_dt = '../DATA'\n",
    "train_data_name = load_data_from_directory(train_load_dt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0b33f09",
   "metadata": {},
   "source": [
    "# 特征工程"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8644a95",
   "metadata": {},
   "source": [
    "## 1. 数据探查 - 掌银金融性流水表(MB_TRNFLW)和掌银非金融性流水表(MB_QRYTRNFLW)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f69d15e0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "掌银金融性流水表(MB_TRNFLW)基本信息:\n",
      "================================================================================\n",
      "数据形状: (15420, 4)\n",
      "\n",
      "数据列名:\n",
      "['DATE', 'CUST_NO', 'TRANSCODE', 'AMOUNT']\n",
      "\n",
      "数据类型:\n",
      "DATE           int64\n",
      "CUST_NO       object\n",
      "TRANSCODE     object\n",
      "AMOUNT       float64\n",
      "dtype: object\n",
      "\n",
      "缺失值统计:\n",
      "DATE         0\n",
      "CUST_NO      0\n",
      "TRANSCODE    0\n",
      "AMOUNT       0\n",
      "dtype: int64\n",
      "\n",
      "前5行数据:\n",
      "       DATE                           CUST_NO  \\\n",
      "0  20250623  01ee82d78213082847766d7058ad4946   \n",
      "1  20250623  3d2edca40ec3f8c4118650bba7e683f2   \n",
      "2  20250623  05f2b207dca253e3f43fac5722083f29   \n",
      "3  20250623  6fe00f0537d3912bf42f071cb372fd61   \n",
      "4  20250623  6fe00f0537d3912bf42f071cb372fd61   \n",
      "\n",
      "                          TRANSCODE    AMOUNT  \n",
      "0  6b2cf76de0b3394f770b446ef87c1639   13600.0  \n",
      "1  6b2cf76de0b3394f770b446ef87c1639     657.0  \n",
      "2  6b2cf76de0b3394f770b446ef87c1639  200000.0  \n",
      "3  6b2cf76de0b3394f770b446ef87c1639   60000.0  \n",
      "4  6b2cf76de0b3394f770b446ef87c1639   65000.0  \n",
      "\n",
      "数据描述统计:\n",
      "               DATE        AMOUNT\n",
      "count  1.542000e+04  1.542000e+04\n",
      "mean   2.025052e+07  2.868671e+04\n",
      "std    8.212173e+01  1.263326e+05\n",
      "min    2.025040e+07  0.000000e+00\n",
      "25%    2.025042e+07  9.646250e+02\n",
      "50%    2.025052e+07  3.600000e+03\n",
      "75%    2.025061e+07  1.400000e+04\n",
      "max    2.025063e+07  3.909441e+06\n"
     ]
    }
   ],
   "source": [
    "# 查看掌银金融性流水表的基本信息\n",
    "print(\"=\"*80)\n",
    "print(\"掌银金融性流水表(MB_TRNFLW)基本信息:\")\n",
    "print(\"=\"*80)\n",
    "print(f\"数据形状: {MB_TRNFLW_data.shape}\")\n",
    "print(f\"\\n数据列名:\\n{MB_TRNFLW_data.columns.tolist()}\")\n",
    "print(f\"\\n数据类型:\\n{MB_TRNFLW_data.dtypes}\")\n",
    "print(f\"\\n缺失值统计:\\n{MB_TRNFLW_data.isnull().sum()}\")\n",
    "print(f\"\\n前5行数据:\\n{MB_TRNFLW_data.head()}\")\n",
    "print(f\"\\n数据描述统计:\\n{MB_TRNFLW_data.describe()}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "baf5e36c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "掌银非金融性流水表(MB_QRYTRNFLW)基本信息:\n",
      "================================================================================\n",
      "数据形状: (241065, 3)\n",
      "\n",
      "数据列名:\n",
      "['DATE', 'CUST_NO', 'TRANSCODE']\n",
      "\n",
      "数据类型:\n",
      "DATE          int64\n",
      "CUST_NO      object\n",
      "TRANSCODE    object\n",
      "dtype: object\n",
      "\n",
      "缺失值统计:\n",
      "DATE         0\n",
      "CUST_NO      0\n",
      "TRANSCODE    0\n",
      "dtype: int64\n",
      "\n",
      "前5行数据:\n",
      "       DATE                           CUST_NO  \\\n",
      "0  20250404  41b53fb71b18d762674f102b68d72ebd   \n",
      "1  20250404  5446a918dd80f1f88635876cf7c0afb3   \n",
      "2  20250404  790027cb32381492ee7c59563c1d5bcf   \n",
      "3  20250404  d9b3669a882cf10e4cbb290c5bffb51e   \n",
      "4  20250404  a77abd2ef0c90cac7c67afeead2593a7   \n",
      "\n",
      "                          TRANSCODE  \n",
      "0  d2b862c923bad173a75f5596c2c07a81  \n",
      "1  ed9ba251abcfb217655212ea08dd3483  \n",
      "2  67f770813fc2e2ddc8ffe875f5905454  \n",
      "3  e2ff32ce4225c81ba4dbd23f5dcb8160  \n",
      "4  d2b862c923bad173a75f5596c2c07a81  \n",
      "\n",
      "数据描述统计:\n",
      "               DATE\n",
      "count  2.410650e+05\n",
      "mean   2.025052e+07\n",
      "std    8.307448e+01\n",
      "min    2.025040e+07\n",
      "25%    2.025042e+07\n",
      "50%    2.025052e+07\n",
      "75%    2.025061e+07\n",
      "max    2.025063e+07\n"
     ]
    }
   ],
   "source": [
    "# 查看掌银非金融性流水表的基本信息\n",
    "print(\"=\"*80)\n",
    "print(\"掌银非金融性流水表(MB_QRYTRNFLW)基本信息:\")\n",
    "print(\"=\"*80)\n",
    "print(f\"数据形状: {MB_QRYTRNFLW_data.shape}\")\n",
    "print(f\"\\n数据列名:\\n{MB_QRYTRNFLW_data.columns.tolist()}\")\n",
    "print(f\"\\n数据类型:\\n{MB_QRYTRNFLW_data.dtypes}\")\n",
    "print(f\"\\n缺失值统计:\\n{MB_QRYTRNFLW_data.isnull().sum()}\")\n",
    "print(f\"\\n前5行数据:\\n{MB_QRYTRNFLW_data.head()}\")\n",
    "print(f\"\\n数据描述统计:\\n{MB_QRYTRNFLW_data.describe()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef2b9725",
   "metadata": {},
   "source": [
    "## 2. 往年赛题特征工程分析总结\n",
    "\n",
    "### 2023年智慧营销第一名方案分析:\n",
    "\n",
    "**掌银金融性流水表(MB_TRNFLW)特征:**\n",
    "1. 交易码唯一数(`trnflw_trn_cd_nunique`)\n",
    "2. 最后交易日期距今天数(`trnflw_last_date`)\n",
    "3. 平均交易金额(`trnflw_mean_amount`)\n",
    "4. 总交易金额(`trnflw_sum_amount`)\n",
    "5. 是否存在周末交易(`trnflw_exist_weekend`)\n",
    "6. 总交易次数(`trnflw_cust_cnt`)\n",
    "7. 周末交易次数(`trnflw_weekend_cnt`)\n",
    "8. 周末交易比率(`trnflw_weekend_ratio`)\n",
    "\n",
    "**掌银非金融性流水表(MB_QRYTRNFLW)特征:**\n",
    "1. 最后查询日期距今天数(`qrytrnflw_last_date`)\n",
    "2. 交易码唯一数(`qry_trn_cd_nunique`)\n",
    "3. 总查询次数(`qrytrnflw_cust_cnt`)\n",
    "4. 周末查询次数(`qrytrnflw_weekend_cnt`)\n",
    "5. 周末查询比率(`qrytrnflw_weekend_ratio`)\n",
    "\n",
    "### 本次特征工程增强策略:\n",
    "- 时间维度: 更细粒度的时间窗口(1天/3天/7天/14天/30天/60天/90天)\n",
    "- 统计维度: 更丰富的统计特征(最大/最小/中位数/标准差/偏度/峰度)\n",
    "- 趋势维度: 时间趋势特征(增长率/波动性/活跃度变化)\n",
    "- 交互维度: 交易码的深度挖掘(频次/金额/时间分布)\n",
    "- 行为维度: 用户行为模式(活跃时段/交易频率/金额分布)\n",
    "- 交叉维度: 金融与非金融流水的交叉特征"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb6e2471",
   "metadata": {},
   "source": [
    "## 3. 特征工程实现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "bbd066bd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "参考日期(最大日期): 2025-06-30 00:00:00\n",
      "数据预处理完成!\n",
      "金融流水数据形状: (15420, 9)\n",
      "非金融流水数据形状: (241065, 8)\n"
     ]
    }
   ],
   "source": [
    "# 数据预处理 - 转换日期格式\n",
    "MB_TRNFLW_data['DATE'] = pd.to_datetime(MB_TRNFLW_data['DATE'], format='%Y%m%d')\n",
    "MB_QRYTRNFLW_data['DATE'] = pd.to_datetime(MB_QRYTRNFLW_data['DATE'], format='%Y%m%d')\n",
    "\n",
    "# 获取数据的最大日期作为参考日期\n",
    "reference_date = MB_TRNFLW_data['DATE'].max()\n",
    "print(f\"参考日期(最大日期): {reference_date}\")\n",
    "\n",
    "# 添加辅助时间特征\n",
    "MB_TRNFLW_data['weekday'] = MB_TRNFLW_data['DATE'].dt.dayofweek  # 0=周一, 6=周日\n",
    "MB_TRNFLW_data['day'] = MB_TRNFLW_data['DATE'].dt.day\n",
    "MB_TRNFLW_data['week'] = MB_TRNFLW_data['DATE'].dt.isocalendar().week\n",
    "MB_TRNFLW_data['is_weekend'] = MB_TRNFLW_data['weekday'].apply(lambda x: 1 if x >= 5 else 0)\n",
    "MB_TRNFLW_data['days_from_ref'] = (reference_date - MB_TRNFLW_data['DATE']).dt.days\n",
    "\n",
    "MB_QRYTRNFLW_data['weekday'] = MB_QRYTRNFLW_data['DATE'].dt.dayofweek\n",
    "MB_QRYTRNFLW_data['day'] = MB_QRYTRNFLW_data['DATE'].dt.day\n",
    "MB_QRYTRNFLW_data['week'] = MB_QRYTRNFLW_data['DATE'].dt.isocalendar().week\n",
    "MB_QRYTRNFLW_data['is_weekend'] = MB_QRYTRNFLW_data['weekday'].apply(lambda x: 1 if x >= 5 else 0)\n",
    "MB_QRYTRNFLW_data['days_from_ref'] = (reference_date - MB_QRYTRNFLW_data['DATE']).dt.days\n",
    "\n",
    "print(\"数据预处理完成!\")\n",
    "print(f\"金融流水数据形状: {MB_TRNFLW_data.shape}\")\n",
    "print(f\"非金融流水数据形状: {MB_QRYTRNFLW_data.shape}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26485a88",
   "metadata": {},
   "source": [
    "### 3.1 掌银金融性流水表(MB_TRNFLW)特征工程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "78be6eef",
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_mb_trnflw_features(df, reference_date):\n",
    "    \"\"\"\n",
    "    创建掌银金融性流水表的详细特征\n",
    "    \n",
    "    参数:\n",
    "    - df: 掌银金融性流水表DataFrame\n",
    "    - reference_date: 参考日期\n",
    "    \n",
    "    返回:\n",
    "    - 特征DataFrame\n",
    "    \"\"\"\n",
    "    print(\"开始构建掌银金融性流水表特征...\")\n",
    "    \n",
    "    features = pd.DataFrame()\n",
    "    \n",
    "    # ===== 1. 基础统计特征 =====\n",
    "    print(\"1. 基础统计特征...\")\n",
    "    \n",
    "    # 1.1 整体统计\n",
    "    basic_stats = df.groupby('CUST_NO').agg({\n",
    "        'AMOUNT': ['count', 'sum', 'mean', 'median', 'std', 'min', 'max', \n",
    "                   lambda x: x.quantile(0.25), lambda x: x.quantile(0.75)],\n",
    "        'TRANSCODE': 'nunique',\n",
    "        'DATE': ['min', 'max'],\n",
    "    }).reset_index()\n",
    "    \n",
    "    basic_stats.columns = ['CUST_NO', \n",
    "                           'mb_trnflw_count', 'mb_trnflw_amount_sum', 'mb_trnflw_amount_mean',\n",
    "                           'mb_trnflw_amount_median', 'mb_trnflw_amount_std', 'mb_trnflw_amount_min',\n",
    "                           'mb_trnflw_amount_max', 'mb_trnflw_amount_q25', 'mb_trnflw_amount_q75',\n",
    "                           'mb_trnflw_transcode_nunique', 'mb_trnflw_first_date', 'mb_trnflw_last_date']\n",
    "    \n",
    "    # 1.2 派生特征\n",
    "    basic_stats['mb_trnflw_amount_range'] = basic_stats['mb_trnflw_amount_max'] - basic_stats['mb_trnflw_amount_min']\n",
    "    basic_stats['mb_trnflw_amount_iqr'] = basic_stats['mb_trnflw_amount_q75'] - basic_stats['mb_trnflw_amount_q25']\n",
    "    basic_stats['mb_trnflw_amount_cv'] = basic_stats['mb_trnflw_amount_std'] / (basic_stats['mb_trnflw_amount_mean'] + 1)\n",
    "    \n",
    "    # 日期特征\n",
    "    basic_stats['mb_trnflw_days_since_first'] = (reference_date - basic_stats['mb_trnflw_first_date']).dt.days\n",
    "    basic_stats['mb_trnflw_days_since_last'] = (reference_date - basic_stats['mb_trnflw_last_date']).dt.days\n",
    "    basic_stats['mb_trnflw_active_days'] = (basic_stats['mb_trnflw_last_date'] - basic_stats['mb_trnflw_first_date']).dt.days + 1\n",
    "    basic_stats['mb_trnflw_freq_per_day'] = basic_stats['mb_trnflw_count'] / (basic_stats['mb_trnflw_active_days'] + 1)\n",
    "    \n",
    "    features = features.merge(basic_stats.drop(['mb_trnflw_first_date', 'mb_trnflw_last_date'], axis=1), \n",
    "                              on='CUST_NO', how='outer') if not features.empty else basic_stats.drop(['mb_trnflw_first_date', 'mb_trnflw_last_date'], axis=1)\n",
    "    \n",
    "    # ===== 2. 时间窗口特征 =====\n",
    "    print(\"2. 时间窗口特征...\")\n",
    "    \n",
    "    time_windows = [1, 3, 7, 14, 30, 60, 90]\n",
    "    for window in time_windows:\n",
    "        df_window = df[df['days_from_ref'] < window]\n",
    "        \n",
    "        if len(df_window) > 0:\n",
    "            window_stats = df_window.groupby('CUST_NO').agg({\n",
    "                'AMOUNT': ['count', 'sum', 'mean', 'max'],\n",
    "                'TRANSCODE': 'nunique'\n",
    "            }).reset_index()\n",
    "            \n",
    "            window_stats.columns = ['CUST_NO',\n",
    "                                    f'mb_trnflw_count_last_{window}d',\n",
    "                                    f'mb_trnflw_amount_sum_last_{window}d',\n",
    "                                    f'mb_trnflw_amount_mean_last_{window}d',\n",
    "                                    f'mb_trnflw_amount_max_last_{window}d',\n",
    "                                    f'mb_trnflw_transcode_nunique_last_{window}d']\n",
    "            \n",
    "            features = features.merge(window_stats, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 3. 周末vs工作日特征 =====\n",
    "    print(\"3. 周末vs工作日特征...\")\n",
    "    \n",
    "    # 周末特征\n",
    "    df_weekend = df[df['is_weekend'] == 1]\n",
    "    if len(df_weekend) > 0:\n",
    "        weekend_stats = df_weekend.groupby('CUST_NO').agg({\n",
    "            'AMOUNT': ['count', 'sum', 'mean'],\n",
    "            'TRANSCODE': 'nunique'\n",
    "        }).reset_index()\n",
    "        weekend_stats.columns = ['CUST_NO', 'mb_trnflw_weekend_count', 'mb_trnflw_weekend_amount_sum',\n",
    "                                 'mb_trnflw_weekend_amount_mean', 'mb_trnflw_weekend_transcode_nunique']\n",
    "        features = features.merge(weekend_stats, on='CUST_NO', how='left')\n",
    "    \n",
    "    # 工作日特征\n",
    "    df_weekday = df[df['is_weekend'] == 0]\n",
    "    if len(df_weekday) > 0:\n",
    "        weekday_stats = df_weekday.groupby('CUST_NO').agg({\n",
    "            'AMOUNT': ['count', 'sum', 'mean'],\n",
    "            'TRANSCODE': 'nunique'\n",
    "        }).reset_index()\n",
    "        weekday_stats.columns = ['CUST_NO', 'mb_trnflw_weekday_count', 'mb_trnflw_weekday_amount_sum',\n",
    "                                 'mb_trnflw_weekday_amount_mean', 'mb_trnflw_weekday_transcode_nunique']\n",
    "        features = features.merge(weekday_stats, on='CUST_NO', how='left')\n",
    "    \n",
    "    # 周末占比\n",
    "    if 'mb_trnflw_weekend_count' in features.columns:\n",
    "        features['mb_trnflw_weekend_ratio'] = features['mb_trnflw_weekend_count'] / (features['mb_trnflw_count'] + 1)\n",
    "        features['mb_trnflw_weekend_amount_ratio'] = features['mb_trnflw_weekend_amount_sum'] / (features['mb_trnflw_amount_sum'] + 1)\n",
    "    \n",
    "    # ===== 4. 交易码(TRANSCODE)深度特征 =====\n",
    "    print(\"4. 交易码深度特征...\")\n",
    "    \n",
    "    # 最常用的交易码\n",
    "    top_transcodes = df['TRANSCODE'].value_counts().head(20).index.tolist()\n",
    "    for idx, transcode in enumerate(top_transcodes):\n",
    "        df_transcode = df[df['TRANSCODE'] == transcode]\n",
    "        if len(df_transcode) > 0:\n",
    "            transcode_stats = df_transcode.groupby('CUST_NO').agg({\n",
    "                'AMOUNT': ['count', 'sum', 'mean']\n",
    "            }).reset_index()\n",
    "            transcode_stats.columns = ['CUST_NO', \n",
    "                                       f'mb_trnflw_top{idx+1}_transcode_count',\n",
    "                                       f'mb_trnflw_top{idx+1}_transcode_amount_sum',\n",
    "                                       f'mb_trnflw_top{idx+1}_transcode_amount_mean']\n",
    "            features = features.merge(transcode_stats, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 5. 金额分布特征 =====\n",
    "    print(\"5. 金额分布特征...\")\n",
    "    \n",
    "    # 不同金额区间的交易次数\n",
    "    amount_bins = [0, 100, 500, 1000, 5000, 10000, 50000, float('inf')]\n",
    "    amount_labels = ['0_100', '100_500', '500_1k', '1k_5k', '5k_10k', '10k_50k', '50k_plus']\n",
    "    \n",
    "    df['amount_bin'] = pd.cut(df['AMOUNT'], bins=amount_bins, labels=amount_labels)\n",
    "    \n",
    "    for label in amount_labels:\n",
    "        df_bin = df[df['amount_bin'] == label]\n",
    "        if len(df_bin) > 0:\n",
    "            amount_bin_count = df_bin.groupby('CUST_NO').size().reset_index(name=f'mb_trnflw_amount_{label}_count')\n",
    "            features = features.merge(amount_bin_count, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 6. 时间趋势特征 =====\n",
    "    print(\"6. 时间趋势特征...\")\n",
    "    \n",
    "    # 按周统计\n",
    "    df_weekly = df.groupby(['CUST_NO', 'week']).agg({\n",
    "        'AMOUNT': ['count', 'sum']\n",
    "    }).reset_index()\n",
    "    \n",
    "    df_weekly.columns = ['CUST_NO', 'week', 'count', 'sum']\n",
    "    \n",
    "    # 计算周交易次数的趋势\n",
    "    weekly_trend = df_weekly.groupby('CUST_NO')['count'].agg(['mean', 'std', 'min', 'max']).reset_index()\n",
    "    weekly_trend.columns = ['CUST_NO', 'mb_trnflw_weekly_count_mean', 'mb_trnflw_weekly_count_std',\n",
    "                            'mb_trnflw_weekly_count_min', 'mb_trnflw_weekly_count_max']\n",
    "    \n",
    "    features = features.merge(weekly_trend, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 7. 活跃度特征 =====\n",
    "    print(\"7. 活跃度特征...\")\n",
    "    \n",
    "    # 活跃天数\n",
    "    active_days = df.groupby('CUST_NO')['DATE'].nunique().reset_index(name='mb_trnflw_active_days_count')\n",
    "    features = features.merge(active_days, on='CUST_NO', how='left')\n",
    "    \n",
    "    # 活跃度 = 活跃天数 / 总天数\n",
    "    features['mb_trnflw_activity_rate'] = features['mb_trnflw_active_days_count'] / (features['mb_trnflw_active_days'] + 1)\n",
    "    \n",
    "    # 连续活跃天数\n",
    "    def calc_consecutive_days(group):\n",
    "        dates = sorted(group['DATE'].unique())\n",
    "        if len(dates) == 0:\n",
    "            return 0\n",
    "        max_consecutive = 1\n",
    "        current_consecutive = 1\n",
    "        for i in range(1, len(dates)):\n",
    "            if (dates[i] - dates[i-1]).days == 1:\n",
    "                current_consecutive += 1\n",
    "                max_consecutive = max(max_consecutive, current_consecutive)\n",
    "            else:\n",
    "                current_consecutive = 1\n",
    "        return max_consecutive\n",
    "    \n",
    "    consecutive_days = df.groupby('CUST_NO').apply(calc_consecutive_days).reset_index(name='mb_trnflw_max_consecutive_days')\n",
    "    features = features.merge(consecutive_days, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 8. 高级统计特征 =====\n",
    "    print(\"8. 高级统计特征...\")\n",
    "    \n",
    "    # 偏度和峰度\n",
    "    from scipy.stats import skew, kurtosis\n",
    "    \n",
    "    skew_kurt = df.groupby('CUST_NO')['AMOUNT'].agg([\n",
    "        ('mb_trnflw_amount_skew', lambda x: skew(x) if len(x) > 1 else 0),\n",
    "        ('mb_trnflw_amount_kurt', lambda x: kurtosis(x) if len(x) > 1 else 0)\n",
    "    ]).reset_index()\n",
    "    \n",
    "    features = features.merge(skew_kurt, on='CUST_NO', how='left')\n",
    "    \n",
    "    # 0金额交易占比\n",
    "    df_zero = df[df['AMOUNT'] == 0]\n",
    "    if len(df_zero) > 0:\n",
    "        zero_amount = df_zero.groupby('CUST_NO').size().reset_index(name='mb_trnflw_zero_amount_count')\n",
    "        features = features.merge(zero_amount, on='CUST_NO', how='left')\n",
    "        features['mb_trnflw_zero_amount_ratio'] = features['mb_trnflw_zero_amount_count'] / (features['mb_trnflw_count'] + 1)\n",
    "    \n",
    "    # ===== 填充缺失值 =====\n",
    "    features = features.fillna(0)\n",
    "    \n",
    "    print(f\"掌银金融性流水表特征构建完成! 特征数量: {len(features.columns) - 1}\")\n",
    "    \n",
    "    return features"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac42f10c",
   "metadata": {},
   "source": [
    "### 3.2 掌银非金融性流水表(MB_QRYTRNFLW)特征工程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "7f265a94",
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_mb_qrytrnflw_features(df, reference_date):\n",
    "    \"\"\"\n",
    "    创建掌银非金融性流水表的详细特征\n",
    "    \n",
    "    参数:\n",
    "    - df: 掌银非金融性流水表DataFrame\n",
    "    - reference_date: 参考日期\n",
    "    \n",
    "    返回:\n",
    "    - 特征DataFrame\n",
    "    \"\"\"\n",
    "    print(\"开始构建掌银非金融性流水表特征...\")\n",
    "    \n",
    "    features = pd.DataFrame()\n",
    "    \n",
    "    # ===== 1. 基础统计特征 =====\n",
    "    print(\"1. 基础统计特征...\")\n",
    "    \n",
    "    # 1.1 整体统计\n",
    "    basic_stats = df.groupby('CUST_NO').agg({\n",
    "        'TRANSCODE': ['count', 'nunique'],\n",
    "        'DATE': ['min', 'max'],\n",
    "    }).reset_index()\n",
    "    \n",
    "    basic_stats.columns = ['CUST_NO', \n",
    "                           'mb_qrytrnflw_count', 'mb_qrytrnflw_transcode_nunique',\n",
    "                           'mb_qrytrnflw_first_date', 'mb_qrytrnflw_last_date']\n",
    "    \n",
    "    # 1.2 派生特征\n",
    "    basic_stats['mb_qrytrnflw_days_since_first'] = (reference_date - basic_stats['mb_qrytrnflw_first_date']).dt.days\n",
    "    basic_stats['mb_qrytrnflw_days_since_last'] = (reference_date - basic_stats['mb_qrytrnflw_last_date']).dt.days\n",
    "    basic_stats['mb_qrytrnflw_active_days'] = (basic_stats['mb_qrytrnflw_last_date'] - basic_stats['mb_qrytrnflw_first_date']).dt.days + 1\n",
    "    basic_stats['mb_qrytrnflw_freq_per_day'] = basic_stats['mb_qrytrnflw_count'] / (basic_stats['mb_qrytrnflw_active_days'] + 1)\n",
    "    \n",
    "    features = features.merge(basic_stats.drop(['mb_qrytrnflw_first_date', 'mb_qrytrnflw_last_date'], axis=1), \n",
    "                              on='CUST_NO', how='outer') if not features.empty else basic_stats.drop(['mb_qrytrnflw_first_date', 'mb_qrytrnflw_last_date'], axis=1)\n",
    "    \n",
    "    # ===== 2. 时间窗口特征 =====\n",
    "    print(\"2. 时间窗口特征...\")\n",
    "    \n",
    "    time_windows = [1, 3, 7, 14, 30, 60, 90]\n",
    "    for window in time_windows:\n",
    "        df_window = df[df['days_from_ref'] < window]\n",
    "        \n",
    "        if len(df_window) > 0:\n",
    "            window_stats = df_window.groupby('CUST_NO').agg({\n",
    "                'TRANSCODE': ['count', 'nunique']\n",
    "            }).reset_index()\n",
    "            \n",
    "            window_stats.columns = ['CUST_NO',\n",
    "                                    f'mb_qrytrnflw_count_last_{window}d',\n",
    "                                    f'mb_qrytrnflw_transcode_nunique_last_{window}d']\n",
    "            \n",
    "            features = features.merge(window_stats, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 3. 周末vs工作日特征 =====\n",
    "    print(\"3. 周末vs工作日特征...\")\n",
    "    \n",
    "    # 周末特征\n",
    "    df_weekend = df[df['is_weekend'] == 1]\n",
    "    if len(df_weekend) > 0:\n",
    "        weekend_stats = df_weekend.groupby('CUST_NO').agg({\n",
    "            'TRANSCODE': ['count', 'nunique']\n",
    "        }).reset_index()\n",
    "        weekend_stats.columns = ['CUST_NO', 'mb_qrytrnflw_weekend_count', 'mb_qrytrnflw_weekend_transcode_nunique']\n",
    "        features = features.merge(weekend_stats, on='CUST_NO', how='left')\n",
    "    \n",
    "    # 工作日特征\n",
    "    df_weekday = df[df['is_weekend'] == 0]\n",
    "    if len(df_weekday) > 0:\n",
    "        weekday_stats = df_weekday.groupby('CUST_NO').agg({\n",
    "            'TRANSCODE': ['count', 'nunique']\n",
    "        }).reset_index()\n",
    "        weekday_stats.columns = ['CUST_NO', 'mb_qrytrnflw_weekday_count', 'mb_qrytrnflw_weekday_transcode_nunique']\n",
    "        features = features.merge(weekday_stats, on='CUST_NO', how='left')\n",
    "    \n",
    "    # 周末占比\n",
    "    if 'mb_qrytrnflw_weekend_count' in features.columns:\n",
    "        features['mb_qrytrnflw_weekend_ratio'] = features['mb_qrytrnflw_weekend_count'] / (features['mb_qrytrnflw_count'] + 1)\n",
    "    \n",
    "    # ===== 4. 交易码(TRANSCODE)深度特征 =====\n",
    "    print(\"4. 交易码深度特征...\")\n",
    "    \n",
    "    # 最常用的交易码\n",
    "    top_transcodes = df['TRANSCODE'].value_counts().head(30).index.tolist()\n",
    "    for idx, transcode in enumerate(top_transcodes):\n",
    "        df_transcode = df[df['TRANSCODE'] == transcode]\n",
    "        if len(df_transcode) > 0:\n",
    "            transcode_count = df_transcode.groupby('CUST_NO').size().reset_index(name=f'mb_qrytrnflw_top{idx+1}_transcode_count')\n",
    "            features = features.merge(transcode_count, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 5. 时间趋势特征 =====\n",
    "    print(\"5. 时间趋势特征...\")\n",
    "    \n",
    "    # 按周统计\n",
    "    df_weekly = df.groupby(['CUST_NO', 'week']).size().reset_index(name='count')\n",
    "    \n",
    "    # 计算周查询次数的趋势\n",
    "    weekly_trend = df_weekly.groupby('CUST_NO')['count'].agg(['mean', 'std', 'min', 'max']).reset_index()\n",
    "    weekly_trend.columns = ['CUST_NO', 'mb_qrytrnflw_weekly_count_mean', 'mb_qrytrnflw_weekly_count_std',\n",
    "                            'mb_qrytrnflw_weekly_count_min', 'mb_qrytrnflw_weekly_count_max']\n",
    "    \n",
    "    features = features.merge(weekly_trend, on='CUST_NO', how='left')\n",
    "    \n",
    "    # 按日统计\n",
    "    df_daily = df.groupby(['CUST_NO', 'DATE']).size().reset_index(name='count')\n",
    "    \n",
    "    # 计算日查询次数的趋势\n",
    "    daily_trend = df_daily.groupby('CUST_NO')['count'].agg(['mean', 'std', 'min', 'max']).reset_index()\n",
    "    daily_trend.columns = ['CUST_NO', 'mb_qrytrnflw_daily_count_mean', 'mb_qrytrnflw_daily_count_std',\n",
    "                           'mb_qrytrnflw_daily_count_min', 'mb_qrytrnflw_daily_count_max']\n",
    "    \n",
    "    features = features.merge(daily_trend, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 6. 活跃度特征 =====\n",
    "    print(\"6. 活跃度特征...\")\n",
    "    \n",
    "    # 活跃天数\n",
    "    active_days = df.groupby('CUST_NO')['DATE'].nunique().reset_index(name='mb_qrytrnflw_active_days_count')\n",
    "    features = features.merge(active_days, on='CUST_NO', how='left')\n",
    "    \n",
    "    # 活跃度 = 活跃天数 / 总天数\n",
    "    features['mb_qrytrnflw_activity_rate'] = features['mb_qrytrnflw_active_days_count'] / (features['mb_qrytrnflw_active_days'] + 1)\n",
    "    \n",
    "    # 连续活跃天数\n",
    "    def calc_consecutive_days(group):\n",
    "        dates = sorted(group['DATE'].unique())\n",
    "        if len(dates) == 0:\n",
    "            return 0\n",
    "        max_consecutive = 1\n",
    "        current_consecutive = 1\n",
    "        for i in range(1, len(dates)):\n",
    "            if (dates[i] - dates[i-1]).days == 1:\n",
    "                current_consecutive += 1\n",
    "                max_consecutive = max(max_consecutive, current_consecutive)\n",
    "            else:\n",
    "                current_consecutive = 1\n",
    "        return max_consecutive\n",
    "    \n",
    "    consecutive_days = df.groupby('CUST_NO').apply(calc_consecutive_days).reset_index(name='mb_qrytrnflw_max_consecutive_days')\n",
    "    features = features.merge(consecutive_days, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 7. 查询模式特征(如果有时间字段) =====\n",
    "    print(\"7. 查询模式特征...\")\n",
    "    \n",
    "    # 注意: 数据中没有小时字段,这里添加每个时段的分布(按天的时段简化处理)\n",
    "    # 按一天分4个时段: 0-6点,6-12点,12-18点,18-24点\n",
    "    # 由于原始数据没有小时,这里简化处理,仅统计早中晚数量分布\n",
    "    \n",
    "    # ===== 8. 查询频率变化特征 =====\n",
    "    print(\"8. 查询频率变化特征...\")\n",
    "    \n",
    "    # 计算前后两个时间窗口的变化率\n",
    "    # 最近7天 vs 前7天\n",
    "    if 'mb_qrytrnflw_count_last_7d' in features.columns and 'mb_qrytrnflw_count_last_14d' in features.columns:\n",
    "        features['mb_qrytrnflw_count_7d_change'] = (features['mb_qrytrnflw_count_last_7d'] - \n",
    "                                                     (features['mb_qrytrnflw_count_last_14d'] - features['mb_qrytrnflw_count_last_7d']))\n",
    "        features['mb_qrytrnflw_count_7d_change_ratio'] = features['mb_qrytrnflw_count_7d_change'] / (\n",
    "            features['mb_qrytrnflw_count_last_14d'] - features['mb_qrytrnflw_count_last_7d'] + 1)\n",
    "    \n",
    "    # 最近30天 vs 前30天\n",
    "    if 'mb_qrytrnflw_count_last_30d' in features.columns and 'mb_qrytrnflw_count_last_60d' in features.columns:\n",
    "        features['mb_qrytrnflw_count_30d_change'] = (features['mb_qrytrnflw_count_last_30d'] - \n",
    "                                                      (features['mb_qrytrnflw_count_last_60d'] - features['mb_qrytrnflw_count_last_30d']))\n",
    "        features['mb_qrytrnflw_count_30d_change_ratio'] = features['mb_qrytrnflw_count_30d_change'] / (\n",
    "            features['mb_qrytrnflw_count_last_60d'] - features['mb_qrytrnflw_count_last_30d'] + 1)\n",
    "    \n",
    "    # ===== 9. 交易码集中度特征 =====\n",
    "    print(\"9. 交易码集中度特征...\")\n",
    "    \n",
    "    # 最常用交易码的占比\n",
    "    transcode_dist = df.groupby(['CUST_NO', 'TRANSCODE']).size().reset_index(name='count')\n",
    "    transcode_max = transcode_dist.groupby('CUST_NO')['count'].max().reset_index(name='mb_qrytrnflw_top1_transcode_count')\n",
    "    \n",
    "    features = features.merge(transcode_max, on='CUST_NO', how='left')\n",
    "    \n",
    "    if 'mb_qrytrnflw_top1_transcode_count' in features.columns:\n",
    "        features['mb_qrytrnflw_top1_transcode_ratio'] = features['mb_qrytrnflw_top1_transcode_count'] / (features['mb_qrytrnflw_count'] + 1)\n",
    "    \n",
    "    # 交易码熵(衡量多样性)\n",
    "    def calc_entropy(group):\n",
    "        counts = group['TRANSCODE'].value_counts()\n",
    "        probs = counts / counts.sum()\n",
    "        entropy = -np.sum(probs * np.log(probs + 1e-10))\n",
    "        return entropy\n",
    "    \n",
    "    transcode_entropy = df.groupby('CUST_NO').apply(calc_entropy).reset_index(name='mb_qrytrnflw_transcode_entropy')\n",
    "    features = features.merge(transcode_entropy, on='CUST_NO', how='left')\n",
    "    \n",
    "    # ===== 填充缺失值 =====\n",
    "    features = features.fillna(0)\n",
    "    \n",
    "    print(f\"掌银非金融性流水表特征构建完成! 特征数量: {len(features.columns) - 1}\")\n",
    "    \n",
    "    return features"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "587b0769",
   "metadata": {},
   "source": [
    "### 3.3 金融与非金融流水交叉特征"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "7dc0f08c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_cross_features(trnflw_features, qrytrnflw_features):\n",
    "    \"\"\"\n",
    "    创建金融与非金融流水的交叉特征\n",
    "    \n",
    "    参数:\n",
    "    - trnflw_features: 金融流水特征DataFrame\n",
    "    - qrytrnflw_features: 非金融流水特征DataFrame\n",
    "    \n",
    "    返回:\n",
    "    - 交叉特征DataFrame\n",
    "    \"\"\"\n",
    "    print(\"开始构建交叉特征...\")\n",
    "    \n",
    "    # 合并两个特征表\n",
    "    cross_features = trnflw_features.merge(qrytrnflw_features, on='CUST_NO', how='outer').fillna(0)\n",
    "    \n",
    "    # ===== 1. 比率特征 =====\n",
    "    print(\"1. 比率特征...\")\n",
    "    \n",
    "    # 金融交易次数 / 非金融查询次数\n",
    "    cross_features['mb_trnflw_qrytrnflw_count_ratio'] = cross_features['mb_trnflw_count'] / (cross_features['mb_qrytrnflw_count'] + 1)\n",
    "    \n",
    "    # 金融交易金额 / 非金融查询次数\n",
    "    cross_features['mb_trnflw_amount_per_qrytrnflw'] = cross_features['mb_trnflw_amount_sum'] / (cross_features['mb_qrytrnflw_count'] + 1)\n",
    "    \n",
    "    # 交易码多样性比较\n",
    "    cross_features['mb_trnflw_qrytrnflw_transcode_ratio'] = cross_features['mb_trnflw_transcode_nunique'] / (cross_features['mb_qrytrnflw_transcode_nunique'] + 1)\n",
    "    \n",
    "    # ===== 2. 活跃度差异特征 =====\n",
    "    print(\"2. 活跃度差异特征...\")\n",
    "    \n",
    "    # 活跃天数差异\n",
    "    cross_features['mb_trnflw_qrytrnflw_active_days_diff'] = cross_features['mb_qrytrnflw_active_days_count'] - cross_features['mb_trnflw_active_days_count']\n",
    "    \n",
    "    # 活跃率差异\n",
    "    cross_features['mb_trnflw_qrytrnflw_activity_rate_diff'] = cross_features['mb_qrytrnflw_activity_rate'] - cross_features['mb_trnflw_activity_rate']\n",
    "    \n",
    "    # 频率差异\n",
    "    cross_features['mb_trnflw_qrytrnflw_freq_diff'] = cross_features['mb_qrytrnflw_freq_per_day'] - cross_features['mb_trnflw_freq_per_day']\n",
    "    \n",
    "    # ===== 3. 时间窗口交叉特征 =====\n",
    "    print(\"3. 时间窗口交叉特征...\")\n",
    "    \n",
    "    time_windows = [7, 14, 30, 60, 90]\n",
    "    for window in time_windows:\n",
    "        if f'mb_trnflw_count_last_{window}d' in cross_features.columns and f'mb_qrytrnflw_count_last_{window}d' in cross_features.columns:\n",
    "            # 比率\n",
    "            cross_features[f'mb_trnflw_qrytrnflw_count_ratio_last_{window}d'] = (\n",
    "                cross_features[f'mb_trnflw_count_last_{window}d'] / (cross_features[f'mb_qrytrnflw_count_last_{window}d'] + 1)\n",
    "            )\n",
    "            \n",
    "            # 差异\n",
    "            cross_features[f'mb_trnflw_qrytrnflw_count_diff_last_{window}d'] = (\n",
    "                cross_features[f'mb_qrytrnflw_count_last_{window}d'] - cross_features[f'mb_trnflw_count_last_{window}d']\n",
    "            )\n",
    "    \n",
    "    # ===== 4. 周末行为交叉特征 =====\n",
    "    print(\"4. 周末行为交叉特征...\")\n",
    "    \n",
    "    # 周末金融交易 / 周末非金融查询\n",
    "    cross_features['mb_trnflw_qrytrnflw_weekend_count_ratio'] = (\n",
    "        cross_features['mb_trnflw_weekend_count'] / (cross_features['mb_qrytrnflw_weekend_count'] + 1)\n",
    "    )\n",
    "    \n",
    "    # 周末占比差异\n",
    "    cross_features['mb_trnflw_qrytrnflw_weekend_ratio_diff'] = (\n",
    "        cross_features['mb_qrytrnflw_weekend_ratio'] - cross_features['mb_trnflw_weekend_ratio']\n",
    "    )\n",
    "    \n",
    "    # ===== 5. 最后活跃时间差异 =====\n",
    "    print(\"5. 最后活跃时间差异...\")\n",
    "    \n",
    "    # 最后交易日期差异\n",
    "    cross_features['mb_trnflw_qrytrnflw_last_date_diff'] = (\n",
    "        cross_features['mb_trnflw_days_since_last'] - cross_features['mb_qrytrnflw_days_since_last']\n",
    "    )\n",
    "    \n",
    "    # 首次活跃日期差异\n",
    "    cross_features['mb_trnflw_qrytrnflw_first_date_diff'] = (\n",
    "        cross_features['mb_trnflw_days_since_first'] - cross_features['mb_qrytrnflw_days_since_first']\n",
    "    )\n",
    "    \n",
    "    # ===== 6. 行为一致性特征 =====\n",
    "    print(\"6. 行为一致性特征...\")\n",
    "    \n",
    "    # 周末行为一致性(都在周末活跃或都不在周末活跃)\n",
    "    cross_features['mb_trnflw_qrytrnflw_weekend_consistency'] = (\n",
    "        (cross_features['mb_trnflw_weekend_ratio'] > 0) & (cross_features['mb_qrytrnflw_weekend_ratio'] > 0)\n",
    "    ).astype(int)\n",
    "    \n",
    "    # 活跃度一致性(都活跃或都不活跃)\n",
    "    cross_features['mb_trnflw_qrytrnflw_activity_consistency'] = (\n",
    "        (cross_features['mb_trnflw_activity_rate'] > 0.5) == (cross_features['mb_qrytrnflw_activity_rate'] > 0.5)\n",
    "    ).astype(int)\n",
    "    \n",
    "    # ===== 7. 复合指标 =====\n",
    "    print(\"7. 复合指标...\")\n",
    "    \n",
    "    # 总活跃度指数 = 金融交易次数 + 非金融查询次数\n",
    "    cross_features['mb_total_activity_index'] = cross_features['mb_trnflw_count'] + cross_features['mb_qrytrnflw_count']\n",
    "    \n",
    "    # 金融活跃度占比\n",
    "    cross_features['mb_trnflw_activity_proportion'] = cross_features['mb_trnflw_count'] / (cross_features['mb_total_activity_index'] + 1)\n",
    "    \n",
    "    # 综合活跃天数\n",
    "    cross_features['mb_total_active_days'] = cross_features['mb_trnflw_active_days_count'] + cross_features['mb_qrytrnflw_active_days_count']\n",
    "    \n",
    "    # ===== 8. 趋势一致性特征 =====\n",
    "    print(\"8. 趋势一致性特征...\")\n",
    "    \n",
    "    # 周统计趋势一致性\n",
    "    if 'mb_trnflw_weekly_count_mean' in cross_features.columns and 'mb_qrytrnflw_weekly_count_mean' in cross_features.columns:\n",
    "        cross_features['mb_weekly_trend_ratio'] = (\n",
    "            cross_features['mb_trnflw_weekly_count_mean'] / (cross_features['mb_qrytrnflw_weekly_count_mean'] + 1)\n",
    "        )\n",
    "    \n",
    "    print(f\"交叉特征构建完成! 新增特征数量: {len(cross_features.columns) - len(trnflw_features.columns) - len(qrytrnflw_features.columns) + 1}\")\n",
    "    \n",
    "    return cross_features"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e74f88ec",
   "metadata": {},
   "source": [
    "## 4. 执行特征工程并保存"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "639c253c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "开始执行特征工程\n",
      "================================================================================\n",
      "\n",
      "【步骤1/3】生成掌银金融性流水表特征...\n",
      "开始构建掌银金融性流水表特征...\n",
      "1. 基础统计特征...\n",
      "2. 时间窗口特征...\n",
      "3. 周末vs工作日特征...\n",
      "4. 交易码深度特征...\n",
      "5. 金额分布特征...\n",
      "6. 时间趋势特征...\n",
      "7. 活跃度特征...\n",
      "8. 高级统计特征...\n",
      "掌银金融性流水表特征构建完成! 特征数量: 140\n",
      "✓ 金融流水特征维度: (1400, 141)\n",
      "\n",
      "【步骤2/3】生成掌银非金融性流水表特征...\n",
      "开始构建掌银非金融性流水表特征...\n",
      "1. 基础统计特征...\n",
      "2. 时间窗口特征...\n",
      "3. 周末vs工作日特征...\n",
      "4. 交易码深度特征...\n",
      "5. 时间趋势特征...\n",
      "6. 活跃度特征...\n",
      "7. 查询模式特征...\n",
      "8. 查询频率变化特征...\n",
      "9. 交易码集中度特征...\n",
      "掌银非金融性流水表特征构建完成! 特征数量: 72\n",
      "✓ 非金融流水特征维度: (3128, 73)\n",
      "\n",
      "【步骤3/3】生成交叉特征...\n",
      "开始构建交叉特征...\n",
      "1. 比率特征...\n",
      "2. 活跃度差异特征...\n",
      "3. 时间窗口交叉特征...\n",
      "4. 周末行为交叉特征...\n",
      "5. 最后活跃时间差异...\n",
      "6. 行为一致性特征...\n",
      "7. 复合指标...\n",
      "8. 趋势一致性特征...\n",
      "交叉特征构建完成! 新增特征数量: 26\n",
      "✓ 最终特征维度: (3128, 239)\n",
      "\n",
      "================================================================================\n",
      "特征工程完成!\n",
      "================================================================================\n",
      "总特征数量: 238\n",
      "客户数量: 3128\n"
     ]
    }
   ],
   "source": [
    "# 执行特征生成\n",
    "print(\"=\"*80)\n",
    "print(\"开始执行特征工程\")\n",
    "print(\"=\"*80)\n",
    "\n",
    "# 1. 运行数据预处理(如果还没运行)\n",
    "try:\n",
    "    if 'weekday' not in MB_TRNFLW_data.columns:\n",
    "        exec(open('preprocessing_code.py').read())\n",
    "except:\n",
    "    pass\n",
    "\n",
    "# 2. 生成掌银金融性流水表特征\n",
    "print(\"\\n【步骤1/3】生成掌银金融性流水表特征...\")\n",
    "mb_trnflw_features = create_mb_trnflw_features(MB_TRNFLW_data, reference_date)\n",
    "print(f\"✓ 金融流水特征维度: {mb_trnflw_features.shape}\")\n",
    "\n",
    "# 3. 生成掌银非金融性流水表特征\n",
    "print(\"\\n【步骤2/3】生成掌银非金融性流水表特征...\")\n",
    "mb_qrytrnflw_features = create_mb_qrytrnflw_features(MB_QRYTRNFLW_data, reference_date)\n",
    "print(f\"✓ 非金融流水特征维度: {mb_qrytrnflw_features.shape}\")\n",
    "\n",
    "# 4. 生成交叉特征\n",
    "print(\"\\n【步骤3/3】生成交叉特征...\")\n",
    "final_features = create_cross_features(mb_trnflw_features, mb_qrytrnflw_features)\n",
    "print(f\"✓ 最终特征维度: {final_features.shape}\")\n",
    "\n",
    "print(\"\\n\" + \"=\"*80)\n",
    "print(\"特征工程完成!\")\n",
    "print(\"=\"*80)\n",
    "print(f\"总特征数量: {len(final_features.columns) - 1}\")\n",
    "print(f\"客户数量: {len(final_features)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "4b734d3c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成的特征列表(前50个):\n",
      "================================================================================\n",
      "1. mb_trnflw_count\n",
      "2. mb_trnflw_amount_sum\n",
      "3. mb_trnflw_amount_mean\n",
      "4. mb_trnflw_amount_median\n",
      "5. mb_trnflw_amount_std\n",
      "6. mb_trnflw_amount_min\n",
      "7. mb_trnflw_amount_max\n",
      "8. mb_trnflw_amount_q25\n",
      "9. mb_trnflw_amount_q75\n",
      "10. mb_trnflw_transcode_nunique\n",
      "11. mb_trnflw_amount_range\n",
      "12. mb_trnflw_amount_iqr\n",
      "13. mb_trnflw_amount_cv\n",
      "14. mb_trnflw_days_since_first\n",
      "15. mb_trnflw_days_since_last\n",
      "16. mb_trnflw_active_days\n",
      "17. mb_trnflw_freq_per_day\n",
      "18. mb_trnflw_count_last_1d\n",
      "19. mb_trnflw_amount_sum_last_1d\n",
      "20. mb_trnflw_amount_mean_last_1d\n",
      "21. mb_trnflw_amount_max_last_1d\n",
      "22. mb_trnflw_transcode_nunique_last_1d\n",
      "23. mb_trnflw_count_last_3d\n",
      "24. mb_trnflw_amount_sum_last_3d\n",
      "25. mb_trnflw_amount_mean_last_3d\n",
      "26. mb_trnflw_amount_max_last_3d\n",
      "27. mb_trnflw_transcode_nunique_last_3d\n",
      "28. mb_trnflw_count_last_7d\n",
      "29. mb_trnflw_amount_sum_last_7d\n",
      "30. mb_trnflw_amount_mean_last_7d\n",
      "31. mb_trnflw_amount_max_last_7d\n",
      "32. mb_trnflw_transcode_nunique_last_7d\n",
      "33. mb_trnflw_count_last_14d\n",
      "34. mb_trnflw_amount_sum_last_14d\n",
      "35. mb_trnflw_amount_mean_last_14d\n",
      "36. mb_trnflw_amount_max_last_14d\n",
      "37. mb_trnflw_transcode_nunique_last_14d\n",
      "38. mb_trnflw_count_last_30d\n",
      "39. mb_trnflw_amount_sum_last_30d\n",
      "40. mb_trnflw_amount_mean_last_30d\n",
      "41. mb_trnflw_amount_max_last_30d\n",
      "42. mb_trnflw_transcode_nunique_last_30d\n",
      "43. mb_trnflw_count_last_60d\n",
      "44. mb_trnflw_amount_sum_last_60d\n",
      "45. mb_trnflw_amount_mean_last_60d\n",
      "46. mb_trnflw_amount_max_last_60d\n",
      "47. mb_trnflw_transcode_nunique_last_60d\n",
      "48. mb_trnflw_count_last_90d\n",
      "49. mb_trnflw_amount_sum_last_90d\n",
      "50. mb_trnflw_amount_mean_last_90d\n",
      "... 还有 188 个特征\n",
      "\n",
      "================================================================================\n",
      "特征总数: 238\n"
     ]
    }
   ],
   "source": [
    "# 查看特征列表\n",
    "print(\"生成的特征列表(前50个):\")\n",
    "print(\"=\"*80)\n",
    "feature_cols = [col for col in final_features.columns if col != 'CUST_NO']\n",
    "for i, col in enumerate(feature_cols[:50], 1):\n",
    "    print(f\"{i}. {col}\")\n",
    "\n",
    "if len(feature_cols) > 50:\n",
    "    print(f\"... 还有 {len(feature_cols) - 50} 个特征\")\n",
    "\n",
    "print(\"\\n\" + \"=\"*80)\n",
    "print(f\"特征总数: {len(feature_cols)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "0ae965cf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✓ 完整特征已保存到: feature\\MB_TRNFLW_QRYTRNFLW_features.pkl\n",
      "✓ 特征列名已保存到: feature\\MB_TRNFLW_QRYTRNFLW_feature_names.txt\n",
      "✓ 金融流水特征已保存到: feature\\MB_TRNFLW_features.pkl\n",
      "✓ 非金融流水特征已保存到: feature\\MB_QRYTRNFLW_features.pkl\n",
      "\n",
      "================================================================================\n",
      "所有特征已成功保存!\n",
      "================================================================================\n"
     ]
    }
   ],
   "source": [
    "# 保存特征到pickle文件\n",
    "import pickle\n",
    "import os\n",
    "\n",
    "# 确保feature目录存在\n",
    "feature_dir = 'feature'\n",
    "if not os.path.exists(feature_dir):\n",
    "    os.makedirs(feature_dir)\n",
    "    print(f\"创建目录: {feature_dir}\")\n",
    "\n",
    "# 保存完整特征数据\n",
    "feature_file = os.path.join(feature_dir, 'MB_TRNFLW_QRYTRNFLW_features.pkl')\n",
    "with open(feature_file, 'wb') as f:\n",
    "    pickle.dump(final_features, f)\n",
    "print(f\"✓ 完整特征已保存到: {feature_file}\")\n",
    "\n",
    "# 保存特征列名\n",
    "feature_names_file = os.path.join(feature_dir, 'MB_TRNFLW_QRYTRNFLW_feature_names.txt')\n",
    "with open(feature_names_file, 'w', encoding='utf-8') as f:\n",
    "    for col in feature_cols:\n",
    "        f.write(col + '\\n')\n",
    "print(f\"✓ 特征列名已保存到: {feature_names_file}\")\n",
    "\n",
    "# 分别保存金融和非金融流水特征(用于后续分析)\n",
    "trnflw_feature_file = os.path.join(feature_dir, 'MB_TRNFLW_features.pkl')\n",
    "with open(trnflw_feature_file, 'wb') as f:\n",
    "    pickle.dump(mb_trnflw_features, f)\n",
    "print(f\"✓ 金融流水特征已保存到: {trnflw_feature_file}\")\n",
    "\n",
    "qrytrnflw_feature_file = os.path.join(feature_dir, 'MB_QRYTRNFLW_features.pkl')\n",
    "with open(qrytrnflw_feature_file, 'wb') as f:\n",
    "    pickle.dump(mb_qrytrnflw_features, f)\n",
    "print(f\"✓ 非金融流水特征已保存到: {qrytrnflw_feature_file}\")\n",
    "\n",
    "print(\"\\n\" + \"=\"*80)\n",
    "print(\"所有特征已成功保存!\")\n",
    "print(\"=\"*80)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05227e90",
   "metadata": {},
   "source": [
    "## 5. 特征统计与验证"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "d095de68",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "特征统计信息\n",
      "================================================================================\n",
      "\n",
      "1. 缺失值检查:\n",
      "✓ 没有缺失值\n",
      "\n",
      "2. 无穷值检查:\n",
      "✓ 没有无穷值\n",
      "\n",
      "3. 特征类型分布:\n",
      "float64    224\n",
      "int64       12\n",
      "int32        2\n",
      "object       1\n",
      "Name: count, dtype: int64\n",
      "\n",
      "4. 部分数值特征统计(前10个):\n",
      "                              count           mean           std  min  25%  \\\n",
      "mb_trnflw_count              3128.0       4.929668  1.608043e+01  0.0  0.0   \n",
      "mb_trnflw_amount_sum         3128.0  141415.922503  1.091376e+06  0.0  0.0   \n",
      "mb_trnflw_amount_mean        3128.0   13214.972623  7.519773e+04  0.0  0.0   \n",
      "mb_trnflw_amount_median      3128.0   11120.389159  7.086400e+04  0.0  0.0   \n",
      "mb_trnflw_amount_std         3128.0    8021.749460  4.190229e+04  0.0  0.0   \n",
      "mb_trnflw_amount_min         3128.0    6386.287289  6.184300e+04  0.0  0.0   \n",
      "mb_trnflw_amount_max         3128.0   29720.761288  1.461123e+05  0.0  0.0   \n",
      "mb_trnflw_amount_q25         3128.0    8398.925456  6.394572e+04  0.0  0.0   \n",
      "mb_trnflw_amount_q75         3128.0   16137.001249  9.221570e+04  0.0  0.0   \n",
      "mb_trnflw_transcode_nunique  3128.0       0.554668  7.417927e-01  0.0  0.0   \n",
      "\n",
      "                             50%           75%           max  \n",
      "mb_trnflw_count              0.0      4.000000  3.810000e+02  \n",
      "mb_trnflw_amount_sum         0.0  21302.915000  3.760978e+07  \n",
      "mb_trnflw_amount_mean        0.0   4772.454545  2.869759e+06  \n",
      "mb_trnflw_amount_median      0.0   3250.000000  2.869759e+06  \n",
      "mb_trnflw_amount_std         0.0   1421.541998  1.323304e+06  \n",
      "mb_trnflw_amount_min         0.0    600.000000  2.869759e+06  \n",
      "mb_trnflw_amount_max         0.0  10000.000000  3.909441e+06  \n",
      "mb_trnflw_amount_q25         0.0   2000.000000  2.869759e+06  \n",
      "mb_trnflw_amount_q75         0.0   5000.000000  2.869759e+06  \n",
      "mb_trnflw_transcode_nunique  0.0      1.000000  6.000000e+00  \n",
      "\n",
      "================================================================================\n"
     ]
    }
   ],
   "source": [
    "# 特征基本统计\n",
    "print(\"=\"*80)\n",
    "print(\"特征统计信息\")\n",
    "print(\"=\"*80)\n",
    "\n",
    "# 检查缺失值\n",
    "print(\"\\n1. 缺失值检查:\")\n",
    "missing_values = final_features.isnull().sum()\n",
    "if missing_values.sum() == 0:\n",
    "    print(\"✓ 没有缺失值\")\n",
    "else:\n",
    "    print(f\"⚠ 发现缺失值:\")\n",
    "    print(missing_values[missing_values > 0])\n",
    "\n",
    "# 检查无穷值\n",
    "print(\"\\n2. 无穷值检查:\")\n",
    "inf_values = np.isinf(final_features.select_dtypes(include=[np.number])).sum()\n",
    "if inf_values.sum() == 0:\n",
    "    print(\"✓ 没有无穷值\")\n",
    "else:\n",
    "    print(f\"⚠ 发现无穷值:\")\n",
    "    print(inf_values[inf_values > 0])\n",
    "\n",
    "# 特征类型分布\n",
    "print(\"\\n3. 特征类型分布:\")\n",
    "print(final_features.dtypes.value_counts())\n",
    "\n",
    "# 数值特征的统计信息(前10个)\n",
    "print(\"\\n4. 部分数值特征统计(前10个):\")\n",
    "numeric_cols = final_features.select_dtypes(include=[np.number]).columns[:10]\n",
    "print(final_features[numeric_cols].describe().T)\n",
    "\n",
    "print(\"\\n\" + \"=\"*80)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "297cf520",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "特征分类统计\n",
      "================================================================================\n",
      "\n",
      "基础统计特征: 25个\n",
      "  示例: ['mb_trnflw_count', 'mb_trnflw_amount_sum', 'mb_trnflw_amount_mean']\n",
      "\n",
      "时间窗口特征: 60个\n",
      "  示例: ['mb_trnflw_count_last_1d', 'mb_trnflw_amount_sum_last_1d', 'mb_trnflw_amount_mean_last_1d']\n",
      "\n",
      "周末工作日特征: 18个\n",
      "  示例: ['mb_trnflw_weekend_count', 'mb_trnflw_weekend_amount_sum', 'mb_trnflw_weekend_amount_mean']\n",
      "\n",
      "交易码特征: 91个\n",
      "  示例: ['mb_trnflw_top1_transcode_count', 'mb_trnflw_top1_transcode_amount_sum', 'mb_trnflw_top1_transcode_amount_mean']\n",
      "\n",
      "时间趋势特征: 13个\n",
      "  示例: ['mb_trnflw_weekly_count_mean', 'mb_trnflw_weekly_count_std', 'mb_trnflw_weekly_count_min']\n",
      "\n",
      "活跃度特征: 14个\n",
      "  示例: ['mb_trnflw_active_days', 'mb_trnflw_active_days_count', 'mb_trnflw_activity_rate']\n",
      "\n",
      "高级统计特征: 3个\n",
      "  示例: ['mb_trnflw_amount_skew', 'mb_trnflw_amount_kurt', 'mb_qrytrnflw_transcode_entropy']\n",
      "\n",
      "交叉特征: 4个\n",
      "  示例: ['mb_trnflw_qrytrnflw_count_ratio', 'mb_trnflw_qrytrnflw_transcode_ratio', 'mb_trnflw_qrytrnflw_freq_diff']\n",
      "\n",
      "其他特征: 10个\n",
      "  示例: ['mb_trnflw_transcode_nunique', 'mb_trnflw_days_since_first', 'mb_trnflw_days_since_last']\n",
      "\n",
      "================================================================================\n",
      "特征总计: 238个\n",
      "================================================================================\n"
     ]
    }
   ],
   "source": [
    "# 特征分类统计\n",
    "print(\"=\"*80)\n",
    "print(\"特征分类统计\")\n",
    "print(\"=\"*80)\n",
    "\n",
    "feature_categories = {\n",
    "    '基础统计特征': [],\n",
    "    '时间窗口特征': [],\n",
    "    '周末工作日特征': [],\n",
    "    '交易码特征': [],\n",
    "    '金额分布特征': [],\n",
    "    '时间趋势特征': [],\n",
    "    '活跃度特征': [],\n",
    "    '高级统计特征': [],\n",
    "    '查询模式特征': [],\n",
    "    '交叉特征': [],\n",
    "    '其他特征': []\n",
    "}\n",
    "\n",
    "for col in feature_cols:\n",
    "    if 'last_' in col and 'd' in col:\n",
    "        feature_categories['时间窗口特征'].append(col)\n",
    "    elif 'weekend' in col or 'weekday' in col:\n",
    "        feature_categories['周末工作日特征'].append(col)\n",
    "    elif 'transcode' in col and 'top' in col:\n",
    "        feature_categories['交易码特征'].append(col)\n",
    "    elif 'amount_' in col and any(x in col for x in ['0-', '100-', '500-', '1k-', '5k-', '10k-', '50k+']):\n",
    "        feature_categories['金额分布特征'].append(col)\n",
    "    elif 'weekly' in col or 'daily' in col or 'trend' in col:\n",
    "        feature_categories['时间趋势特征'].append(col)\n",
    "    elif 'active' in col or 'consecutive' in col or 'activity' in col:\n",
    "        feature_categories['活跃度特征'].append(col)\n",
    "    elif 'skew' in col or 'kurt' in col or 'entropy' in col:\n",
    "        feature_categories['高级统计特征'].append(col)\n",
    "    elif any(x in col for x in ['morning', 'afternoon', 'evening', 'night', 'hour']):\n",
    "        feature_categories['查询模式特征'].append(col)\n",
    "    elif 'trnflw_qrytrnflw' in col or 'cross' in col or 'total' in col:\n",
    "        feature_categories['交叉特征'].append(col)\n",
    "    elif any(x in col for x in ['count', 'sum', 'mean', 'median', 'std', 'min', 'max', 'q25', 'q75', 'range', 'iqr', 'cv']):\n",
    "        feature_categories['基础统计特征'].append(col)\n",
    "    else:\n",
    "        feature_categories['其他特征'].append(col)\n",
    "\n",
    "for category, features in feature_categories.items():\n",
    "    if len(features) > 0:\n",
    "        print(f\"\\n{category}: {len(features)}个\")\n",
    "        print(f\"  示例: {features[:3]}\")\n",
    "\n",
    "print(\"\\n\" + \"=\"*80)\n",
    "print(f\"特征总计: {sum(len(v) for v in feature_categories.values())}个\")\n",
    "print(\"=\"*80)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3ead0aa",
   "metadata": {},
   "source": [
    "## 6. 特征工程总结\n",
    "\n",
    "### 特征工程完成情况\n",
    "\n",
    "✅ **总特征数量: 238个特征**\n",
    "- 金融流水特征: 140个\n",
    "- 非金融流水特征: 72个  \n",
    "- 交叉特征: 26个\n",
    "\n",
    "✅ **覆盖客户数: 3,128个**\n",
    "\n",
    "### 特征分类详细说明\n",
    "\n",
    "#### 1. **基础统计特征 (25个)**\n",
    "- 交易次数、金额统计(总和、均值、中位数、标准差、最大最小值、四分位数)\n",
    "- 交易码唯一数\n",
    "- 时间跨度特征(首次/最后交易距今天数、活跃天数、日均频率)\n",
    "- 派生特征(金额范围、IQR、变异系数CV)\n",
    "\n",
    "#### 2. **时间窗口特征 (60个)**\n",
    "- 7个时间窗口: 1天、3天、7天、14天、30天、60天、90天\n",
    "- 每个窗口统计: 交易次数、金额统计、交易码多样性\n",
    "- 金融流水: 35个特征\n",
    "- 非金融流水: 14个特征\n",
    "- 交叉窗口特征: 11个特征\n",
    "\n",
    "#### 3. **周末工作日特征 (18个)**\n",
    "- 周末/工作日分别统计交易次数、金额\n",
    "- 周末占比、周末金额占比\n",
    "- 交易码在周末/工作日的分布\n",
    "- 周末行为一致性特征\n",
    "\n",
    "#### 4. **交易码深度特征 (91个)**\n",
    "- 金融流水Top20交易码: 每个交易码的次数、金额统计(60个)\n",
    "- 非金融流水Top30交易码: 每个交易码的查询次数(30个)\n",
    "- 交易码集中度: 最常用交易码占比\n",
    "- 交易码熵: 衡量交易码多样性\n",
    "\n",
    "#### 5. **时间趋势特征 (13个)**\n",
    "- 按周统计: 周均交易次数、标准差、最大最小值\n",
    "- 按日统计: 日均查询次数、标准差、最大最小值\n",
    "- 趋势变化: 近7天vs前7天、近30天vs前30天\n",
    "\n",
    "#### 6. **活跃度特征 (14个)**\n",
    "- 活跃天数统计\n",
    "- 活跃率 = 活跃天数/总天数\n",
    "- 最长连续活跃天数\n",
    "- 金融与非金融活跃度对比\n",
    "\n",
    "#### 7. **高级统计特征 (3个)**\n",
    "- 金额分布的偏度(Skewness)\n",
    "- 金额分布的峰度(Kurtosis)\n",
    "- 交易码熵(Entropy)\n",
    "\n",
    "#### 8. **金额分布特征 (7个)**\n",
    "- 7个金额区间: 0-100, 100-500, 500-1k, 1k-5k, 5k-10k, 10k-50k, 50k+\n",
    "- 每个区间的交易次数统计\n",
    "\n",
    "#### 9. **交叉特征 (26个)**\n",
    "- 金融/非金融比率: 交易次数比、交易码比\n",
    "- 活跃度差异: 活跃天数差、活跃率差、频率差\n",
    "- 时间窗口交叉: 各窗口的比率和差异\n",
    "- 周末行为交叉: 周末比率差异\n",
    "- 时间差异: 最后活跃时间差、首次活跃时间差\n",
    "- 行为一致性: 周末一致性、活跃度一致性\n",
    "- 复合指标: 总活跃度、金融活跃占比\n",
    "\n",
    "### 特征工程亮点\n",
    "\n",
    "1. **时间维度充分挖掘**\n",
    "   - 多时间窗口(7个窗口)\n",
    "   - 时间趋势变化\n",
    "   - 周末vs工作日对比\n",
    "\n",
    "2. **交易码深度挖掘**\n",
    "   - Top交易码统计\n",
    "   - 交易码集中度\n",
    "   - 交易码多样性(熵)\n",
    "\n",
    "3. **金融与非金融交叉**\n",
    "   - 26个交叉特征\n",
    "   - 行为一致性分析\n",
    "   - 多维度对比\n",
    "\n",
    "4. **统计特征全面**\n",
    "   - 基础统计(均值、方差等)\n",
    "   - 高级统计(偏度、峰度、熵)\n",
    "   - 金额分布特征\n",
    "\n",
    "5. **活跃度多角度**\n",
    "   - 活跃天数、活跃率\n",
    "   - 连续活跃天数\n",
    "   - 跨表活跃度对比\n",
    "\n",
    "### 特征保存文件\n",
    "\n",
    "1. **完整特征数据**: `feature/MB_TRNFLW_QRYTRNFLW_features.pkl` (238特征)\n",
    "2. **特征名称列表**: `feature/MB_TRNFLW_QRYTRNFLW_feature_names.txt`\n",
    "3. **金融流水特征**: `feature/MB_TRNFLW_features.pkl` (140特征)\n",
    "4. **非金融流水特征**: `feature/MB_QRYTRNFLW_features.pkl` (72特征)\n",
    "\n",
    "### 后续建议\n",
    "\n",
    "1. **特征筛选**: 使用特征重要性分析,筛选出最有价值的特征\n",
    "2. **特征组合**: 可以进一步探索非线性特征组合\n",
    "3. **时序特征**: 可以添加更多时序趋势特征(如移动平均、指数平滑等)\n",
    "4. **用户分群**: 基于特征进行用户聚类,构建分群特征\n",
    "5. **特征降维**: 使用PCA/t-SNE等方法进行特征降维"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "starcup",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
